package g3pd.gradep.virdgm;

import java.io.*;
import java.util.*;
import java.util.logging.Level;
import java.util.logging.Logger;

import org.isam.exehda.Exehda;
import org.isam.exehda.HostId;
import org.isam.exehda.ObjectId;
import org.isam.exehda.services.CellInformationBase;
import org.isam.exehda.services.Executor;
import org.isam.exehda.services.Worb;

public class VirdLauncher {
    
    HostId[] hosts;
    ObjectId[] objectIds;
    
    CellInformationBase cib;
    Executor executor;
    Worb worb;
    
    VirdMemLoader virdMemLoader;
    VirdProcLoader virdProcLoader;
    
    Vector<Integer> memorySpace = new Vector<Integer>();
    Vector<VirdProcElem> procLocked = new Vector<VirdProcElem>();
    Vector<VirdProcElem> procReady = new Vector<VirdProcElem>();
    Vector<HostId> freeNodes = new Vector<HostId>();
    int [][] adjMatrix;
    
    public static void main(String[] args) throws IOException {
        try {
            checkInputArgs(args);
            //final VirdLauncher virdLauncher = new VirdLauncher(args[0], args[1], "hostid:0.note");
            final VirdLauncher virdLauncher = new VirdLauncher(args[0], args[1], args[2]);
            Thread vird = new Thread() {
                @Override
            	public void run() {
                    virdLauncher.scheduler();
            	}
            };
            
            vird.start(); // To check
            vird.join();
            
            //Thread.sleep(1000);
            virdLauncher.printMemoryLog();
            
        } catch (InterruptedException ex) {
            Logger.getLogger(VirdLauncher.class.getName()).log(Level.SEVERE, null, ex);
        }
    }
    
    public VirdLauncher(String memInputFile, String procInputFile, String nodesToExec) throws IOException {
	    virdMemLoader = new VirdMemLoader(memInputFile);
        virdProcLoader = new VirdProcLoader(procInputFile);
        
        procLocked = virdProcLoader.getVirdProcElem();
        adjMatrix = virdProcLoader.getAdjMatrix();
        
    	VirdLauncher.logger("procReady " + procReady.size() );
        
        for(int i=0; i < procLocked.size(); i++) {
            VirdProcElem virdProcElem = procLocked.get(i);
            VirdLauncher.logger("i = " + i);
            if( ckeckReadyProc(virdProcElem.getProcID()) ) {
            	VirdLauncher.logger(" checkProc ");
                procReady.add(procLocked.remove(i));
                i--;
            }
        }
        
    	VirdLauncher.logger("procReady " + procReady.size() );
        
        int []tempMemory = virdMemLoader.getDateAttr();
        for(int j=0;j<tempMemory.length;j++) {
            memorySpace.add(tempMemory[j]);
        }
        
        String myHosts = nodesToExec;
        String hostNames[] = myHosts.split(";");
        
        hosts = new HostId[hostNames.length];
        objectIds = new ObjectId[procLocked.size() + procReady.size()];
        
        cib = (CellInformationBase) Exehda.getService(CellInformationBase.SERVICE_NAME);
        executor = (Executor) Exehda.getService(Executor.SERVICE_NAME);
        worb = (Worb) Exehda.getService(Worb.SERVICE_NAME);
        
        for (int i = 0; i < hosts.length; i++) {
            hosts[i] = HostId.parseId(hostNames[i]);
            freeNodes.add(hosts[i]);
        }
    }
    
    public void scheduler() {
        final Semaphore procDone = new Semaphore(1-procReady.size()-procLocked.size());
        try {
            while (!procLocked.isEmpty() || !procReady.isEmpty()) {
                VirdLauncher.logger("Entrou no while");
                VirdLauncher.logger("freeNodes " + freeNodes.size());
                VirdLauncher.logger("procReady " + procReady.size());

                if (!freeNodes.isEmpty() && !procReady.isEmpty()) {
                    VirdLauncher.logger("Passou pelo IF");

                    final HostId hID = freeNodes.remove(0);
                    VirdProcElem virdProcElem = procReady.remove(0);

                    final int procID = virdProcElem.getProcID();
                    final String actionAttr = virdProcElem.getActionAttr();
                    final int outputPosAttr = virdProcElem.getOutputPosAttr();
                    final int[] inputPosAttr = virdProcElem.getInputPosAttr();
                    final int[] memoryValues = new int[inputPosAttr.length];

                    for (int i = 0; i < inputPosAttr.length; i++) {
                        memoryValues[i] = memorySpace.get(inputPosAttr[i]);
                    }

                    new Thread() {
                        @Override
                        public void run() {
                            objectIds[procID] = executor.createObject(VirdExecImpl.class, new Object[]{}, new VirdActivator(), hID);
                            String serviceURI = cib.getAttribute(hID.toResourceName(), "contactAddress" + objectIds[procID].toResourceName().toString());
                            final VirdExec virdExec = (VirdExec) worb.lookupService(serviceURI, VirdExec.class);

                            Serializable result = virdExec.exec(actionAttr, memoryValues);
                            memorySpace.set(outputPosAttr, (Integer) result);
                            releaseProcToExec(procID);

                            for (int i = 0; i < procLocked.size(); i++) {
                                if (ckeckReadyProc(procLocked.get(i).getProcID())) {
                                    procReady.add(procLocked.remove(i));
                                    i--;
                                }
                            }

                            freeNodes.add(hID);
                            
                            procDone.v();

                            synchronized (freeNodes) {
                                freeNodes.notify();
                            }
                        }
                    }.start();
                } else {
                    synchronized (freeNodes) {
                        freeNodes.wait(5000);
                    }
                }
            }

            procDone.p();
        } catch (Exception ex) {
            Logger.getLogger(VirdLauncher.class.getName()).log(Level.SEVERE, null, ex);
        }
    }
    
    public void releaseProcToExec(int procID) {
        for(int column = 0; column < this.adjMatrix.length; column++)
            this.adjMatrix[procID][column] = 0;
    }
    
    public boolean ckeckReadyProc(int procID) {
        for(int line = 0; line < this.adjMatrix.length; line++)
            if(this.adjMatrix[line][procID] == 1)
                return false;
        
        return true;
    }
    
    private static void checkInputArgs(String [] args) throws IOException {
        if(args.length < 3) {
            VirdLauncher.logger(args[0] + args[1] + args[2]);
            VirdLauncher.logger("Usage: memfile.xml procfile.xml nodesToExec");
            System.exit(-1);
        }
    }
    
    public void printMemoryLog() throws IOException {
        String message = "Memory State: ";
        for(int i = 0; i < memorySpace.size(); i++)
            message += memorySpace.get(i);
        
        VirdLauncher.logger(message);
    }
    
    private static void logger(String message) throws IOException {
        BufferedWriter logFile = new BufferedWriter(new FileWriter("vird-gm.log", true));
        logFile.write(message + "\n");
        logFile.flush();
        logFile.close();
    }
}